package gtpengine;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;

import playout_policy.RandomPolicy;

import exceptions.AlreadyOccupiedException;
import exceptions.GameOverException;
import exceptions.IllegalMoveException;
import exceptions.ImpossibleMoveException;
import exceptions.KoViolationException;
import exceptions.SuicideMoveException;
import exceptions.UnknownColorException;

import game.GoBoard;
import game.GoGame;
import mcts.GoGameTree;
import mcts.GoGameTreeSonsList;

public class GtpEngine {

	GoGameTree tree;
	float komi;
	int boardsize;
	private int last_color_play;
	final static long TIME_LIMIT = 10000;

	final private static String[] known_commands = {"protocol_version","name","version","known_command","list_command","quit","boardsize","clear_board","komi","play","genmove","game_state"};

	public GtpEngine() {
		this.boardsize = 9;
		this.komi = (float) 6.5;
		resetGame();
		last_color_play = GoBoard.WHITE;
		GoGameTree.setPlayoutPolicy(new RandomPolicy());
	}

	private void resetGame() {
		tree = new GoGameTree(new GoGame(this.boardsize,this.komi));
	}

	private String setKomi(float komi) {
		this.komi = komi;
		resetGame();
		return "=\n\n";
	}

	private String setBoardsize(int size) {
		this.boardsize = size;
		resetGame();
		return "=\n\n";
	}

	private String getProtocolVersion() {
		return "= 2\n\n";
	}

	private String getName() {
		return "= GoEngineTrial\n\n";
	}

	private String getVersion() {
		return "= 0.1\n\n";
	}

	private String knownCommand(String cmd) {
		for (String c : known_commands) {
			if (cmd.equals(c)) return "= true\n\n";
		}
		return "= false\n\n";
	}

	private String listCommands() {
		String res = "= ";
		for (String c : known_commands) res += c+"\n";
		return res+"\n";
	}

	private void quit() {
		System.exit(0);
	}

	private String printGameState() {
		return tree.getGame().printInfo("#");
	}

	private String play(int color, int position) {
		if (color == last_color_play) {
			//TODO
			System.err.println("Consecutive plays not implemented yet");
			return "";
		}
		
		GoGameTreeSonsList sons = tree.getSons();
		LinkedList<Integer> examined_moves = new LinkedList<Integer>();
		if (sons != null) {
			for (GoGameTree s : sons) {
				examined_moves.add(s.getLastMove());
			}
		}
		GoGame game = tree.getGame();
		
		this.last_color_play = color;
		
		String comment = "";
		
		if (sons == null || !examined_moves.contains(position)) {
			//comment = "# This variation was not considered ...\n";
			try {
				game.move(position);
			} catch (IllegalMoveException e) {
				return "? invalid point \n\n";
			} catch (ImpossibleMoveException e) {
				return "? invalid point \n\n";
			} catch (GameOverException e) {
				return "= \n\n";
			}
			tree = new GoGameTree(game);
		} else {
			GoGameTree corresponding_subtree = null;
			for (GoGameTree s : sons) {
				if (s.getLastMove() == position) {
					corresponding_subtree = s;
				}
			}
			//comment = "# This variation was considered ... Reusing "+corresponding_subtree.getSize()+" of "+tree.getSize()+" nodes.\n";
			tree = corresponding_subtree;
		}
		return comment+"= \n\n";
	}
	
	private String genmove() {
		long init_time = System.currentTimeMillis();
		long current_time = init_time;
		int simulations = 1;
		
		
		//TODO more refined version to take care of time limit more precisely
		while (current_time < init_time + TIME_LIMIT) {
			try {
				tree.triggerOneSimulation(simulations);
			} catch (GameOverException e) {
			}
			simulations+=1;
			current_time = System.currentTimeMillis();
		}
		tree = tree.getMostRobustSon();
		try {
			last_color_play = GoBoard.reverseColor(last_color_play);
		} catch (UnknownColorException e) {
			return "? unknown color";
		}
		return "= "+tree.getLastMoveLabel()+"\n\n";
	}

	private String clearBoard() {
		this.resetGame();
		return "= \n\n";
	}
	
	private String executeCommand(String cmdString) {
		String[] components = cmdString.split(" ");
		
		String command = components[0];
		
		if (command.equals("protocol_version")) {
			return this.getProtocolVersion();
		} else if (command.equals("name")) {
			return this.getName();
		} else if (command.equals("version")) {
			return this.getVersion();
		} else if (command.equals("known_command")) {
			if (components.length == 1) return "? command need one argument\n\n";
			return this.knownCommand(components[1]);
		} else if (command.equals("list_commands")) {
			return this.listCommands();
		} else if (command.equals("quit")) {
			this.quit();
			return "";
		} else if (command.equals("boardsize")) {
			if (components.length == 1) return "? command need one argument\n\n";
			return this.setBoardsize(Integer.valueOf(components[1]));
		} else if (command.equals("clear_board")) {
			return this.clearBoard();
		} else if (command.equals("komi")) {
			if (components.length == 1) return "? command need one argument\n\n";
			return this.setKomi(Float.valueOf(components[1]));
		} else if (command.equals("play")) {
			if (components.length < 2) return "? command need two arguments\n\n";
			
			int color = 0;
			if (components[1].equalsIgnoreCase("w")) color = GoBoard.WHITE;
			else if (components[1].equalsIgnoreCase("b")) color = GoBoard.BLACK;
			else return "? argument 1 must be black or white\n\n";
			
			try {
				int position = tree.getGame().getBoard().labelToIntPosition(components[2]);
				return this.play(color, position);
			} catch (ImpossibleMoveException e) {
				e.printStackTrace();
				return "? invalid move "+components[2]+"\n\n";
				
			}
				
		} else if (command.equals("genmove")) {
			return this.genmove();
		} else if (command.equals("game_state")) {
			return this.printGameState();
		} else {
			return "? unknown command "+command+"\n\n";
		}
		
	}
	
	
	
	public static void main(String[] args) {
		GtpEngine engine = new GtpEngine();
		
		while(true) {
			BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

			String commandString = null;

			try {
				commandString = br.readLine();
				String res = engine.executeCommand(commandString);
				System.out.print(res);
			} catch (IOException ioe) {
				System.err.println("IO error ; just quit");
				System.exit(1);
			}
		}
	}

}
